﻿// Dependencies:
//  1. JSBuilder
//  2. JQHelper
//  3. jQuery
//  4. jQuery.UI (used for DateTimer, Tabs, and MakeJQButton)

using System.Text;

namespace BigfootMVC.Helpers
{

    /// <summary>
    /// This class helps us to create javascript code using server side code for commonly performed 
    /// operation. Although I do not like inline javascript, there are some times in the interested
    /// of productivity when it helps to make these items accessbile and repeatable. The funcitons  
    /// in this helper are particularly useful when using in conjunction with the HtmlHelper and
    /// tag builder classes.
    /// 
    /// It abstracts repetive tasks when making ajax calls, validating input, and adding functionality
    /// not currently present in asp.net in an easily repeatable manner.
    /// </summary>
    public class JSHelper
    {

        /// <summary>
        /// Factory method
        /// </summary>
        public static JSHelper Create()
        {
            return new JSHelper();
        }

        /// <summary>
        /// Returns a string ready to be used in a literal expression in javascript
        /// </summary>
        public string CleanString(string value)
        {
            return JSBuilder.CleanString(value);
        }

        /// <summary>
        /// Outputs a jquery statement to show an element
        /// </summary>
        /// <param name="elementId">The elment id with or without the # selector</param>
        public JQHelper ShowElement(string elementId)
        {
            return JQHelper.Create().SelectID(elementId).show();
        }

        /// <summary>
        /// Outputs a jquery statement to show an element and hide another
        /// </summary>
        /// <param name="showelement">The elment id to show with or without the # selector</param>
        /// <param name="hideelement">The element id to show with or without the # selector</param>
        public JQHelper ShowAndHideElement(string showelement, string hideelement)
        {
            return JQHelper.Create().SelectID(showelement).show().SelectID(hideelement).hide();
        }

        /// <summary>
        /// Outputs a jquery statement to hide an element
        /// </summary>
        /// <param name="elementId">The elment id with or without the # selector</param>
        public JQHelper HideElement(string elementId)
        {
            return JQHelper.Create().SelectID(elementId).hide();
        }

        /// <summary>
        /// Outputs a jquery statement to clear the innerHtml of an element
        /// </summary>
        /// <param name="elementId">The elment id with or without the # selector</param>
        public JQHelper ClearElement(string elementId)
        {
            return JQHelper.Create().SelectID(elementId).clear();
        }

        /// <summary>
        /// Outputs a jquery statement to show an element if it is hidden, or hide it if it is showing
        /// </summary>
        /// <param name="elementId">The elment id with or without the # selector</param>
        public JQHelper ToggleElement(string elementId)
        {
            return JQHelper.Create().SelectID(elementId).toggle();
        }


        #region "SubmitForm"

        /// <summary>
        /// Submits form, and does validation.
        /// </summary>
        public JSBuilder SubmitForm(string formId)
        {
            return SubmitForm(formId, true);
        }

        /// <summary>
        /// Submits form, and optionally does validation.
        /// </summary>
        public JSBuilder SubmitForm(string formId, bool Validate)
        {
            var js = new JSBuilder();
            js.AddLine(JQ.SelectID(formId).trigger("submit").ToString());
            if (Validate)
                js.WrapFormValidation(formId);
            return js;
        }


        /// <summary>
        /// Submits the form and does validation on the inputs contained within the containerId
        /// </summary>
        public JSBuilder SubmitForm_ValidateContainer(string formId, string containerId)
        {
            var js = new JSBuilder();
            js.AddLine(JQ.SelectID(formId).trigger("submit").ToString());
            js.WrapContainerValidation(containerId);
            return js;
        }

        #endregion


        #region "SubmitParentForm"

        /// <summary>
        /// Submits the parent form of the the elment that invokes this script and validates it.
        /// </summary>
        public JSBuilder SubmitParentForm()
        {
            return SubmitParentForm(true);
        }
        
        /// <summary>
        /// Submits the parent form of the the elment that invokes this script. 
        /// Optionally does validation
        /// </summary>
        public JSBuilder SubmitParentForm(bool validate)
        {
            var js = JSBuilder.Create();
            js.AddLine(JQ.SelectParentForm().trigger("submit").ToString());
            if (validate) js.WrapParentFormValidation();
            return js;
        }

        /// <summary>
        /// Submits the parent form of the the elment that invokes this script. Validates the input elements within the specified container
        /// </summary>
        public JSBuilder SubmitParentForm_ValidateContainer(string containerId)
        {
            var js = JSBuilder.Create();
            js.AddLine(JQ.SelectParentForm().trigger("submit").ToString());
            js.WrapContainerValidation(containerId);
            return js;
        }

        #endregion


        #region "SubmitParentFormViaAjax"

        /// <summary>
        /// Submits the parent form of this element via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. And performs validation
        /// </summary>
        public JSBuilder SubmitParentFormViaAjax(string postUrl, string updatePanel)
        {
            return SubmitParentFormViaAjax(postUrl, updatePanel, true);
        }

        /// <summary>
        /// Submits the parent form of this element via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. Optionally does validation
        /// </summary>
        public JSBuilder SubmitParentFormViaAjax(string postUrl, string updatePanel, bool validate)
        {
            var script = new JSBuilder();
            script.AddLine(JQ.SelectParentForm().ajaxSubmit(postUrl, updatePanel).ToString());
            if (validate)
                script.WrapParentFormValidation();
            return script;
        }

        /// <summary>
        /// Submits the parent form of this element via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. Does validation for the inputs contained within a the specified containerId.
        /// </summary>
        public JSBuilder SubmitParentFormViaAjax_ValidateContainer(string postUrl, string updatePanel, string containerId)
        {
            var script = new JSBuilder();
            script.AddLine(JQ.SelectParentForm().ajaxSubmit(postUrl, updatePanel).ToString());
            script.WrapContainerValidation(containerId);
            return script;
        }

        #endregion
        

        #region "SubmitFormViaAjax"

        /// <summary>
        /// Submits specified form via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. Validates before sedning.
        /// </summary>
        public JSBuilder SubmitFormViaAjax(string formId, string postUrl, string updatePanel)
        {
            return SubmitFormViaAjax(formId, postUrl, updatePanel, true);
        }

        /// <summary>
        /// Submits specified form via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. Can optionally perform validation
        /// </summary>
        public JSBuilder SubmitFormViaAjax(string formId, string postUrl, string updatePanel, bool validate)
        {
            var js = new JSBuilder();
            js.AddLine(JQ.SelectID(formId).ajaxSubmit(postUrl, updatePanel).ToString());
            if (validate) js.WrapFormValidation(formId);
            return js;
        }

        /// <summary>
        /// Submits specified form via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. It validates the inputs within the specified container.
        /// </summary>
        public JSBuilder SubmitFormViaAjax_ValidateContainer(string formId, string postUrl, string updatePanel, string containerId)
        {
            var js = new JSBuilder();
            js.AddLine(JQ.SelectID(formId).ajaxSubmit(postUrl, updatePanel).ToString());
            if (string.IsNullOrEmpty(containerId) != false)
                js.WrapContainerValidation(containerId);
            return js;
        }

        /// <summary>
        /// Submits specified form via ajax to the url specified, and updates the supplied updatePanel with the value
        /// returned from the server. It validates the specified inputs before submitting.
        /// </summary>
        public JSBuilder SubmitFormViaAjax_ValidateInputs(string formId, string postUrl, string updatePanel, params string[] validateInputs)
        {
            var js = new JSBuilder();
            js.AddLine(JQ.SelectID(formId).ajaxSubmit(postUrl, updatePanel).ToString());
            if (validateInputs != null && validateInputs.Length > 0)
                js.WrapInputsValidation(validateInputs);
            return js;
        }

        #endregion
        

        /// <summary>
        /// Sets the focus to the element specified
        /// </summary>
        public string SetFocus(string elementId)
        {
            return JQHelper.Create().SelectID(elementId).AddCommand("focus()").ToScriptBlock();
        }

        /// <summary>
        /// Performs an ajax request and loads the content of the supplied url into the elementid specified.
        /// You can also optionally toggle the contents, this means that if the content is showing the
        /// ajax call is not performed and the elementid's content is cleared and hidden. When the element
        /// is hidden then the ajax request happens and the the fills the element and shows it
        /// </summary>
        /// <param name="url">Url to request from</param>
        /// <param name="elementid">The element that will hold the response</param>
        /// <param name="toggle">When true retreives the content and fills the element only when it is not showing</param>
        public JSBuilder Load(string url, string elementid, bool toggle)
        {
            var js = new JSBuilder();
            if (toggle)
            {
                js.Add("if(jQuery('#" + elementid + "').is(':visible')) { ");
                js.Add("    jQuery('#" + elementid + "').html('').hide(); ");
                js.Add("} else { ");
                js.Add("    jQuery('#" + elementid + "').load('" + JSBuilder.CleanString(url) + "').show();");
                js.Add("} ");
            }
            else
            {
                js.Add("jQuery('#" + elementid + "').load('" + JSBuilder.CleanString(url) + "').show();");
            }
            return js;
        }

        /// <summary>
        /// Creates an html element into a virtual form, or a partial form. It outputs the correct scripts to wrap validation
        /// and form submission around the inputs inside the supplied virtual form element. By default all elements inside the virtual form
        /// with a class assigned named causesValidation trigger a validation call whenever clicked
        /// i.e. to setup a partial form you call CreatePartialForm("addUser") where addUser is the name of a div
        ///      to then submit the form you simply call SubmitPartialForm("addUser", "/users/createuser") when this happens only the inputs 
        ///      within the div.addUser are validated and submitted via ajax to url specified
        /// </summary>
        /// <param name="formId">The element id that wraps the group of inputs you want to make into a form</param>
        /// <returns>Returns a function named ValidateGroup_{formId} which is called by the SubmitPartialForm method to validate before submitting a form</returns>
        public string CreatePartialForm(string formId)
        {
            if (formId.StartsWith("#"))
                formId = formId.Substring(1);
            var validateFunctionName = "ValidateGroup_" + formId;
            var setupFunctionName = "CreatePartialForm_" + formId;
            var setupRetryVariable = setupFunctionName + "_Retry";
            var script = new StringBuilder();
            script.AppendLine("<script type='text/javascript'>");
            script.AppendLine(" var " + setupRetryVariable + " = 0;");
            script.AppendLine(" jQuery(document).ready(function() {");
            script.AppendLine("     setTimeout(" + setupFunctionName + ", 1);");
            script.AppendLine(" });");
            script.AppendLine(" function " + setupFunctionName + "(){");
            script.AppendLine("     if (typeof(jQuery.fn.validate) == 'undefined' && " + setupRetryVariable + " < 5) { bigfootMVC.log('retryValidate'); " + setupRetryVariable + " += 1; setTimeout(" + setupFunctionName + ", 500); }");
            script.AppendLine("     jQuery('#" + formId + "').parents('form:eq(0)').validate({onsubmit: false});");
            script.AppendLine("     jQuery('#" + formId + " .causesValidation').click(" + validateFunctionName + ");");
            script.AppendLine("     jQuery('#" + formId + " :text').keydown(function(evt) { if (evt.keyCode == 13) { " + validateFunctionName + "(evt); } });");
            script.AppendLine(" }");
            script.AppendLine(" function " + validateFunctionName + "(evt) {");
            script.AppendLine("     var isValid = true;");
            script.AppendLine("     jQuery('#" + formId + "').find(':input').each(function(i, item) { if (!jQuery(item).valid()) isValid = false; });");
            script.AppendLine("     if (!isValid && evt) evt.preventDefault();");
            script.AppendLine("     return isValid;");
            script.AppendLine(" }");
            script.AppendLine("</script>");
            return script.ToString();
        }

        /// <summary>
        /// Creates an html element into a virtual form, or a partial form. It outputs the correct scripts to wrap validation
        /// and form submission around the inputs inside the supplied virtual form element. By default all elements inside the virtual form
        /// with a class assigned named causesValidation trigger a validation call whenever clicked
        /// i.e. to setup a partial form you call CreatePartialForm("addUser") where addUser is the name of a div
        ///      to then submit the form you simply call SubmitPartialForm("addUser", "/users/createuser") when this happens only the inputs 
        ///      within the div.addUser are validated and submitted via ajax to url specified
        /// </summary>
        /// <param name="formId">The element id that wraps the group of inputs you want to make into a form</param>
        /// <param name="addScriptTag">Determines whether to add the script tag</param>
        /// <returns>Returns a function named ValidateGroup_{formId} which is called by the SubmitPartialForm method to validate before submitting a form</returns>
        public string CreatePartialForm(string formId, bool addScriptTag)
        {
            if (formId.StartsWith("#"))
                formId = formId.Substring(1);
            var validateFunctionName = "ValidateGroup_" + formId;
            var setupFunctionName = "CreatePartialForm_" + formId;
            var setupRetryVariable = setupFunctionName + "_Retry";
            var script = new StringBuilder();
            if (addScriptTag) script.AppendLine("<script type='text/javascript'>");
            script.AppendLine(" var " + setupRetryVariable + " = 0;");
            script.AppendLine(" jQuery(document).ready(function() {");
            script.AppendLine("     setTimeout(" + setupFunctionName + ", 1);");
            script.AppendLine(" });");
            script.AppendLine(" function " + setupFunctionName + "(){");
            script.AppendLine("     if (typeof(jQuery.fn.validate) == 'undefined' && " + setupRetryVariable + " < 5) { bigfootMVC.log('retryValidate'); " + setupRetryVariable + " += 1; setTimeout(" + setupFunctionName + ", 500); }");
            script.AppendLine("     jQuery('#" + formId + "').parents('form:eq(0)').validate({onsubmit: false});");
            script.AppendLine("     jQuery('#" + formId + " .causesValidation').click(" + validateFunctionName + ");");
            script.AppendLine("     jQuery('#" + formId + " :text').keydown(function(evt) { if (evt.keyCode == 13) { " + validateFunctionName + "(evt); } });");
            script.AppendLine(" }");
            script.AppendLine(" function " + validateFunctionName + "(evt) {");
            script.AppendLine("     var isValid = true;");
            script.AppendLine("     jQuery('#" + formId + "').find(':input').each(function(i, item) { if (!jQuery(item).valid()) isValid = false; });");
            script.AppendLine("     if (!isValid && evt) evt.preventDefault();");
            script.AppendLine("     return isValid;");
            script.AppendLine(" }");
            if (addScriptTag) script.AppendLine("</script>");
            return script.ToString();
        }

        /// <summary>
        /// Submits a previously setup partial form through CreatePartialForm method to the url specified
        /// </summary>
        /// <param name="formId">The element id containing the partial form</param>
        /// <param name="url">The url you want to submit the partial form to</param>
        public AjaxRequest SubmitPartialForm(string formId, string url)
        {
            return SubmitPartialForm(formId, url, true);
        }

        /// <summary>
        /// Submits a previously setup partial form through CreatePartialForm method to the url specified
        /// </summary>
        /// <param name="formId">The element id containing the partial form</param>
        /// <param name="url">The url you want to submit the partial form to</param>
        /// <param name="validate">Determines weather to validate before submission or not. By default all elements within
        /// the parial form that have the class causesValidation trigger a validation call when clicked</param>
        public AjaxRequest SubmitPartialForm(string formId, string url, bool validate)
        {
            var ajax = AjaxRequest.Create(url);
            ajax.Post().PartialForm(formId);
            if (validate)
                ajax.Validate();
            return ajax;
        }

        public string CreateDatePicker(string inputName)
        {
            return JQID(inputName).AddCommand("datepicker()").ToScriptBlock();
        }

        public string CreateDatePicker(string inputName, string dateFormat)
        {
            return JQID(inputName).AddCommand("datepicker({dateFormat: '" + dateFormat + "'})").ToScriptBlock();
        }

        /// <summary>
        /// Makes a jquery UI button with the provided selector and uiClass
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        public string MakeJQButton(string selector)
        {
            return MakeJQButton(selector, "", true);
        }

        /// <summary>
        /// Makes a jquery UI button with the provided with a 
        /// .jqButton selector and ui-icon-circle-triangle-e as the icon
        /// </summary>
        public string MakeJQButton()
        {
            return MakeJQButton(".jqButton", "ui-icon-circle-triangle-e", true);
        }

        /// <summary>
        /// Makes a jquery UI button with the provided selector and uiClass
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        /// <param name="uiClass">The UI Class if any to assign to the button</param>
        /// <returns></returns>
        public string MakeJQButton(string selector, string uiClass)
        {
            return MakeJQButton(selector, uiClass, true);
        }
        
        /// <summary>
        /// Makes a jquery UI button with the provided selector and uiClass
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        /// <param name="uiClass">The UI Class if any to assign to the button</param>
        /// <param name="includeScriptTag">Determines whether to include the script tag</param>
        /// <returns></returns>
        public string MakeJQButton(string selector, string uiClass, bool includeScriptTag)
        {
            var builder = (string.IsNullOrEmpty(uiClass)) ? JQ.Select(selector).AddCommand("button()") 
                                                          : JQ.Select(selector).AddCommand("button({ icons: {primary: '" + uiClass +"'} })");
            return includeScriptTag ? builder.ToScriptBlock() 
                                    : builder.ToString();
        }

        /// <summary>
        /// Configures a jquery UI Tabs with the provided selector
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        public string MakeJQTabs(string selector)
        {
            return MakeJQTabs(selector, string.Empty, null, true);
        }

        /// <summary>
        /// Configures a jquery UI Tabs with the provided selector
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        /// <param name="selectFunction">The function to be called when a tab is selected. Receives two parameters (event, ui)</param>
        public string MakeJQTabs(string selector, string selectFunction)
        {
            return MakeJQTabs(selector, selectFunction, null, true);
        }

        /// <summary>
        /// Configures a jquery UI Tabs with the provided selector
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        /// <param name="selectFunction">The function to be called when a tab is selected. Receives two parameters (event, ui)</param>
        /// <param name="selectedTabIndex">The tab to select by default</param>
        public string MakeJQTabs(string selector, string selectFunction, int selectedTabIndex)
        {
            return MakeJQTabs(selector, selectFunction, new JSObject().Add("selected", selectedTabIndex), true);
        }

        /// <summary>
        /// Configures a jquery UI Tabs with the provided selector
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        /// <param name="includeScriptTag">Determines whether to include the script tag</param>
        /// <param name="selectFunction">The function to be called when a tab is selected. Receives two parameters (event, ui)</param>
        public string MakeJQTabs(string selector, string selectFunction, bool includeScriptTag)
        {
            return MakeJQTabs(selector, selectFunction, null, includeScriptTag);
        }

        /// <summary>
        /// Configures a jquery UI Tabs with the provided selector
        /// </summary>
        /// <param name="selector">The jQuery selector</param>
        /// <param name="otherOptions">Add options to the tabs</param>
        /// <param name="includeScriptTag">Determines whether to include the script tag</param>
        /// <param name="selectFunction">The function to be called when a tab is selected. Receives two parameters (event, ui)</param>
        public string MakeJQTabs(string selector, string selectFunction, JSObject otherOptions, bool includeScriptTag)
        {
            var options = new JSObject();
            if (!string.IsNullOrEmpty(selectFunction)) options.Add("select", selectFunction);
            if (otherOptions != null) options.Merge(otherOptions);

            var builder = options.IsEmpty() ? JQ.Select(selector).AddCommand("tabs()") :
                                              JQ.Select(selector).AddCommand("tabs(" + options + ")");
            return includeScriptTag ? builder.ToScriptBlock()
                                    : builder.ToString();
        }
        
        private JQHelper JQ { get { return new JQHelper(); } }
        private JQHelper JQID(string id) { return JQ.SelectID(id); }
    }
}
